package magent

import (
	"fmt"
	"log"
	"net"
	"time"

	"strconv"

	"encoding/json"

	"github.com/kataras/iris"
	"github.com/spf13/viper"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// OpLog for operation log.
type OpLog struct {
	ID        int    `json:"_id" bson:"_id"`
	Operation string `json:"type" bson:"operation"`
	User      string `json:"username" bson:"user"`
	Time      string `json:"time"`
	IP        string `json:"ip"`
}

type resErrMap map[string]interface{}

func genErrMap(status int, message string) resErrMap {
	errMap := make(resErrMap)
	errMap["statusCode"] = status
	errMap["message"] = message
	return errMap
}

func getOpLogs() ([]OpLog, string, error) {
	session := globalSession.Clone()
	defer session.Close()

	// Get filter from.
	from, err := getLast("oplog")
	if err != nil {
		return nil, "", err
	}

	// Get filter to.
	to := time.Now().Format("2006-01-02 15:04")

	opLogs := make([]OpLog, 10)
	query := bson.M{"time": bson.M{"$gte": from, "$lt": to}}
	err = session.DB(dbName).C("logs").Find(query).All(&opLogs)
	if err != nil {
		return nil, "", err
	}
	return opLogs, to, nil
}

func queryLogs(ctx *iris.Context) {
	opLogs, to, err := getOpLogs()
	if err != nil {
		log.Panic(err)
	}
	if err := ctx.JSON(200, opLogs); err != nil {
		log.Panic(err)
	}
	err = updateLast("oplog", to)
	if err != nil {
		log.Panic(err)
	}
}

// Info of windfarm, windturbine, district, facilitator
type Info struct {
	Name string `json:"name"`
}

// TodoInfo structure.
type TodoInfo struct {
	District    Info   `bson:"district"`
	Windfarm    Info   `bson:"windfarm"`
	Windturbine Info   `bson:"windturbine"`
	Facilitator Info   `bson:"facilitator"`
	LatestTime  string `bson:"latest_data_time"`
	Number      string `bson:"serial_number"`
}

// Todo structure.
type Todo struct {
	Info TodoInfo `bson:"info"`
}

// WindturbineNoti for offline winturbine notification.
type WindturbineNoti struct {
	District    string `json:"district"`
	Windfarm    string `json:"windfarm"`
	Windturbine string `json:"windturbine"`
	Facilitator string `json:"facilitator"`
	LatestTime  string `json:"latest_data_time,omitempty"`
	Type        string `json:"type"`
	Number      string `json:"todo_no,omitempty"`
	ErrorCode   int    `json:"error_code"`
}

func getOffWTs() ([]WindturbineNoti, string, error) {
	session := globalSession.Clone()
	defer session.Close()

	fromStr, err := getLast("offline_todo")
	if err != nil {
		return nil, "", err
	}
	location, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		return nil, "", err
	}
	from, err := time.ParseInLocation("2006-01-02 15:04", fromStr, location)
	if err != nil {
		return nil, "", err
	}
	// Get filter to.
	to := time.Now()

	wtNotis := make([]WindturbineNoti, 0)
	var todo Todo
	query := bson.M{"info.created_time": bson.M{"$gte": from.Unix(), "$lt": to.Unix()}}
	iter := session.DB(dbName).C("todos").Find(query).Iter()
	if iter.Next(&todo) {
		noti := WindturbineNoti{
			todo.Info.District.Name,
			todo.Info.Windfarm.Name,
			todo.Info.Windturbine.Name,
			todo.Info.Facilitator.Name,
			todo.Info.LatestTime,
			"cms",
			todo.Info.Number,
			5003}
		wtNotis = append(wtNotis, noti)
	}
	if err != nil {
		return nil, "", err
	}
	return wtNotis, to.Format("2006-01-02 15:04"), nil

}

// Windturbine structure.
type Windturbine struct {
	Name        string `bson:"name"`
	District    Info   `bson:"district"`
	Windfarm    Info   `bson:"windfarm"`
	Facilitator Info   `bson:"facilitator"`
}

func getErrorScada() ([]WindturbineNoti, error) {
	session := globalSession.Clone()
	defer session.Close()

	var (
		wt      Windturbine
		wtNotis = make([]WindturbineNoti, 0)
	)
	iter := session.DB(dbName).C("windturbines").Find(nil).Iter()
	for iter.Next(&wt) {
		scada := session.DB(dbName).C("scada").Find(bson.M{"windturbine_name": wt.Name})
		count, err := scada.Count()
		if err != nil {
			return nil, fmt.Errorf("Cannot get scada info of %s. \n%v", wt.Name, err)
		}
		// log.Printf("%s count: %d", wt.Name, count)
		if count == 0 {
			noti := WindturbineNoti{
				wt.District.Name,
				wt.Windfarm.Name,
				wt.Name,
				wt.Facilitator.Name,
				"",
				"scada",
				"",
				5005}
			wtNotis = append(wtNotis, noti)
		}
	}
	return wtNotis, nil

}

// TimeHealth map. For exmaple:
// day health
// {
//     "2016-12-01" : 100,
//     "2016-12-02" : 100,
// }
//
// month health
// {
// 	   "2016-09" : 97,
// 	   "2016-10" : 100
// }
// quarter health
// {
//     "2016#3" : 97,
//     "2016#4" : 100
// }
//
// year health
// {
// 	   "2016" : 98
// }
//
type TimeHealth map[string]int

// HealthType has Windfarm, District and Country(null struct).
type HealthType struct {
	// ID   int    `json:"_id" bson:"_id"`
	Name string `json:"name"`
}

// Health structure.
type Health struct {
	// ID int `json:"_id" bson:"_id"`
	// Day      TimeHealth  `json:"day"`
	Month TimeHealth `json:"month"`
	// Quarter  TimeHealth  `json:"quarter"`
	// Year     TimeHealth  `json:"year"`
	Windfarm *HealthType `json:"windfarm"`
	District *HealthType `json:"district"`
}

// HealthNoti created when health value greater than threshold.
type HealthNoti struct {
	Month     string `json:"month"`
	Value     int    `json:"value"`
	Type      string `json:"type"`
	Name      string `json:"name"`
	ErrorCode int    `json:"error_code"`
}

func getLatestHealth(timeHealth TimeHealth) (string, int) {
	var health int
	latestMonth := ""
	for month, value := range timeHealth {
		if month > latestMonth {
			health = value
			latestMonth = month
		}
	}
	return latestMonth, health
}

func appendHealthNotis(healthNotis []HealthNoti, healthType string, threshold int, session *mgo.Session) []HealthNoti {
	var (
		health Health
		name   string
	)
	selector := bson.M{"day": 0, "quarter": 0, "year": 0}
	coll := fmt.Sprintf("health.%v", healthType)
	iter := session.DB(dbName).C(coll).Find(nil).Select(selector).Iter()
	for iter.Next(&health) {
		if len(health.Month) == 0 {
			continue
		}
		month, value := getLatestHealth(health.Month)
		if value <= threshold {
			switch healthType {
			case "windfarm":
				name = health.Windfarm.Name
			case "district":
				name = health.District.Name
			case "china":
				name = "china"
			}
			healthNotis = append(healthNotis, HealthNoti{month, value, healthType, name, 5005})
		}
	}
	return healthNotis
}

func getHealthNotis(threshold int) []HealthNoti {
	session := globalSession.Clone()
	defer session.Close()

	healthNotis := make([]HealthNoti, 0)
	for _, healthType := range []string{"windfarm", "district", "china"} {
		healthNotis = appendHealthNotis(healthNotis, healthType, threshold, session)
	}
	return healthNotis
}

func queryHealth(ctx *iris.Context) {
	var err error

	// Get threshold.
	threshold := viper.GetInt("HealthThreshold")
	thresholdParm := ctx.URLParam("threshold")
	if thresholdParm != "" {
		threshold, err = strconv.Atoi(thresholdParm)
		if err != nil {
			errMsg := fmt.Sprintf("Invalid filter threshold %v, it must be integer.", thresholdParm)
			ctx.Log(errMsg)
			if err := ctx.JSON(400, genErrMap(400, errMsg)); err != nil {
				log.Panic(err)
			}
			return
		}
	}
	healthNotis := getHealthNotis(threshold)
	if err := ctx.JSON(200, healthNotis); err != nil {
		log.Panic(err)
	}
}

// ErrorLogNoti for error log notifiaction.
type ErrorLogNoti struct {
	User      string `json:"username" bson:"user"`
	IP        string `json:"ip"`
	Time      string `json:"time"`
	Path      string `json:"path"`
	Message   string `json:"msg" bson:"message"`
	ErrorCode int    `json:"error_code" bson:"error_code"`
	Type      string `json:"type"`
}

func getErrorLogs() ([]ErrorLogNoti, string, error) {
	session := globalSession.Clone()
	defer session.Close()

	// Get filter from.
	from, err := getLast("error_log")
	if err != nil {
		return nil, "", err
	}

	// Get filter to.
	to := time.Now().Format("2006-01-02 15:04")

	errLogs := make([]ErrorLogNoti, 0)
	var errLog ErrorLogNoti
	query := bson.M{"time": bson.M{"$gte": from, "$lt": to}}
	iter := session.DB(dbName).C("error_logs").Find(query).Iter()
	for iter.Next(&errLog) {
		errLog.Type = "server"
	}
	return errLogs, to, nil
}

func notiErrorLog(ctx *iris.Context) {
	var errNoti ErrorLogNoti
	if err := ctx.ReadJSON(&errNoti); err != nil {
		log.Panic(err)
	}
	errNoti.ErrorCode = 5000

	go func() {
		url := fmt.Sprintf("%v/log_notifications", viper.GetString("MonitorHost"))
		body, err := json.Marshal(errNoti)
		if err != nil {
			log.Panic(err)
		}

		retry := 0
		for retry < 3 {
			resp, err := PostJSON(url, body)
			if err != nil || resp.StatusCode != 200 {
				retry = retry + 1
			} else {
				break
			}
		}
		if retry >= 3 {
			ctx.Log("Cannot send notification to monitor.")
		} else {
			ctx.Log("Sended notification to monitor.")
		}
	}()

	if err := ctx.Text(200, "Success."); err != nil {
		log.Panic(err)
	}
}

// Server with host and name
type Server struct {
	host, name string
}

// ServerStat to show web server status.
type ServerStat struct {
	PLM       string `json:"plm_status"`
	CMS       string `json:"cms_status"`
	FILE      string `json:"file_status"`
	ErrorCode int    `json:"error_code"`
	Time      string `json:"time"`
}

func getServerStat() (*ServerStat, error) {
	now := time.Now()
	servers, err := getServers()
	if err != nil {
		return nil, err
	}

	var serverStat ServerStat
	// var wg sync.WaitGroup
	for _, server := range servers {
		// wg.Add(1)
		_, err := net.Dial("tcp", server.host)
		status := "normal"
		if err != nil {
			status = "abnormal"
		}
		switch server.name {
		case "plm":
			serverStat.PLM = status
		case "cms":
			serverStat.CMS = status
		case "file":
			serverStat.FILE = status
		}
		// wg.Done()
	}
	// wg.Wait()
	if serverStat.PLM == "normal" && serverStat.CMS == "normal" && serverStat.FILE == "normal" {
		serverStat.ErrorCode = -1
	} else {
		serverStat.ErrorCode = 5002
	}

	serverStat.Time = time.Now().Format("2006-01-02 15:04")
	log.Println("Func exec ", time.Now().Sub(now))
	return &serverStat, nil
}

func severStatHandler(ctx *iris.Context) {
	serverStat, err := getServerStat()
	if err != nil {
		if err := ctx.JSON(500, genErrMap(500, "Cannot get server stat.")); err != nil {
			log.Panic(err)
		}
		return
	}
	if err := ctx.JSON(200, serverStat); err != nil {
		log.Panic(err)
	}
}
